package luzhin;

import java.util.*;
import luzhin.MoveGenerator.MovesAndAttacks;
import org.apache.log4j.Logger;

/**
 * The heart of the chess engine.
 * Class representing strategy making moves by backtracking possible moves and evaluating
 * leaves.
 * 
 * TODO print thinking
 * 
 * @author lacungus
 *
 */
public class BacktrackStrategy extends Strategy {
    private static Logger logger = Logger.getLogger(BacktrackStrategy.class);

	private boolean isCacheOn = false;
	private static final long serialVersionUID = 3894238947L;
	private Evaluator evaluator;
	// Maximal depth of recursion.
	private int maxDepth = 5;
	private Move moveToMake = null;
	private MoveValidator moveValidator = null;
	private AttackDiagramBuilder diagramBuilder = null;
	private SimpleMoveGenerator generator = null;
    private MoveGenerator moveGenerator;
	private SearchCache searchCache;
	
	private int nodesSearched = 0;
	private int nodesFoundInCache = 0;
	
	private long startTime;
	private long finTime;
    private double resultEvaluation;
    
	/**
	 * 
	 * @param generator Move generator.
	 * @param evaluator Evaluator.
	 */
	public BacktrackStrategy(SimpleMoveGenerator generator, Evaluator evaluator) {
		this.generator = generator;
		this.evaluator = evaluator;
		this.moveValidator = new AdvancedMoveValidator();
		this.diagramBuilder = new AdvancedAttackDiagramBuilder();
		this.searchCache = new SearchCache(1, maxDepth);
        this.moveGenerator = new MoveGenerator();
	}
	
	private CacheData searchInCache(Board board, int depth) {
		return searchCache.get(board, depth);
	}
	
	private void printLog() {
		logger.info("Evaluation = " + resultEvaluation);
		logger.debug("Nodes searched: " + nodesSearched);
		logger.debug("Nodes found in cache: " + nodesFoundInCache);
		logger.debug("Efficiency: " + (double) nodesFoundInCache / nodesSearched);
		long timeSpent = finTime - startTime;
		logger.debug("Time = " + timeSpent / 1000 + "." + (timeSpent % 1000) + " seconds");
		
		logger.debug("Boards asked: " + BoardPool.getBoardsAsked());
		logger.debug("Boards reused: " + BoardPool.getBoardsReused());
		logger.debug("Max boards in pool: " + BoardPool.getMaxBoardsInPool());
	}
	
	/**
	 * Makes a move. 
	 * @param board 
	 * @return Move to make. null - resign.
	 */
	public Move makeMove(Board board) {
		startTime = System.currentTimeMillis();
		resultEvaluation = rec(board, maxDepth, -evaluator.getInf(), evaluator.getInf());
		
		if (!moveValidator.isValid(board, moveToMake))
			moveToMake = null;
		finTime = System.currentTimeMillis();
		
		printLog();
		nodesSearched = 0;
		nodesFoundInCache = 0;
		return moveToMake;
	}

	/**
	 * @return max depth.
	 */
	public int getMaxDepth() {
		return maxDepth;
	}
	
	/**
	 * Set max depth.
	 */
	public void setMaxDepth(int maxDepth) {
		this.maxDepth = maxDepth;
	}

    public boolean isCacheOn() {
        return isCacheOn;
    }

    public void setIsCacheOn(boolean isCacheOn) {
        this.isCacheOn = isCacheOn;
    }
	/**
	 * Recursion
	 * @param board
	 * @param depth current depth
	 * @param a - alpha
	 * @param b - beta
	 * @return evaluation
	 */
	private double rec(Board board, int depth, double a, double b) {
		++nodesSearched;
        MovesAndAttacks movesAndAttacks = moveGenerator.generate(board);
//		AttackDiagram diagram = diagramBuilder.buildAttackDiagram(board);
		AttackDiagram diagram = movesAndAttacks.getAttackDiagram();
	
		if (depth == 0) {
			double curRes = evaluator.evaluate(board, diagram);
			BoardPool.addBoard(board);
			return curRes;
		}
	
		if (isCacheOn) {
			CacheData data = searchInCache(board, depth);
			if (data != null) {
				nodesFoundInCache++;
				return data.getEval();
			}
		}
		
		boolean whiteToMove = board.isWhiteToMove();
		if (whiteToMove && diagram.isCheckToBlack())
			return evaluator.getInf();
		if (!whiteToMove && diagram.isCheckToWhite())
			return evaluator.getInf();
				
		double currentBestEval = -evaluator.getInf();
		//double currentBestEval = -a;

//		LinkedList<Move> moves = generator.generateMoves(board, diagram);
		List<Move> moves = movesAndAttacks.getMoves();
		Move bestFoundMove = null;
		for (Move move : moves) {
			Board newb = board.applyMove(move);
			double newEval = -rec(newb, depth - 1, -b, -currentBestEval);

			if (depth == maxDepth && newEval > currentBestEval) {
				moveToMake = move;
			}
			if (newEval > currentBestEval) {
				currentBestEval = newEval;
				bestFoundMove = move;
			}
			if (currentBestEval >= b) {
				if (isCacheOn) {
					searchCache.put(board, new CacheData(bestFoundMove, depth, currentBestEval));
				}
				return currentBestEval;
			}
		}

		if (isCacheOn) {
			searchCache.put(board, new CacheData(bestFoundMove, depth, currentBestEval));
		}
		return currentBestEval;				
	}
	
}
